﻿using gt.rediscachemanager.Configuration;
using gt.rediscachemanager.Core.Route;
using gt.rediscachemanager.Entry;
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace gt.rediscachemanager.Core.ClientServer
{
    public class RedisClientServerPool : IDisposable
    {
        private ConcurrentDictionary<string, RedisClientServer> m_pool = new ConcurrentDictionary<string, RedisClientServer>();
        private IRedisNodeRouter Router { get; set; }

        public string Name { get; private set; }
        public PoolMode Mode { get; private set; }

        public RedisClientServerPool(CachePoolConfiguration configuration)
        {
            if (configuration == null) throw new ArgumentNullException("configuration");
            if (string.IsNullOrEmpty(configuration.Name)) throw new ArgumentNullException("configuration.Name");

            Name = configuration.Name;
            Mode = configuration.Mode;
            Router = CreateRouter(configuration);

            List<Task> tasks = new List<Task>();
            configuration.Nodes.ForEach(x =>
            {
                var t = Task.Run(() =>
                {
                    var cs = new RedisClientServer(x);
                    var key = BuildKey(x.Name);

                    if (m_pool.ContainsKey(key))
                        throw new InvalidOperationException(string.Format("redisclientserver pool:{0} already exist nodename:{1}.", Name, x.Name));
                    if (!m_pool.TryAdd(key, cs))
                        throw new InvalidOperationException(string.Format("redisclientserver pool:{0} addnodename:{1} failed.", Name, x.Name));
                });
                tasks.Add(t);
            });
            Task.WaitAll(tasks.ToArray());
        }

        public RedisClientServer GetClientServerByNodeName(string nodeName)
        {
            RedisClientServer cs = null;
            var key = BuildKey(nodeName);
            m_pool.TryGetValue(key, out cs);
            return cs;
        }
        public RedisClientServer GetClientServer(string key)
        {
            var nodeName = Router.GetNodeName(key);
            RedisClientServer cs = null;
            m_pool.TryGetValue(BuildKey(nodeName), out cs);
            return cs;
        }

        public ICollection<RedisClientServer> GetAllRedisClientServer()
        {
            return m_pool.Values;
        }

        private string BuildKey(string nodeName)
        {
            return string.Concat(Name, "_", nodeName);
        }
        private IRedisNodeRouter CreateRouter(CachePoolConfiguration configuration)
        {
            IRedisNodeRouter router = null;
            switch (configuration.Mode)
            {
                case PoolMode.Single:
                    router = new SingleNodeRouter(new RouteData { NodeName = configuration.Nodes.First().Name });
                    break;
                case PoolMode.Multiple:
                    var routes = configuration.Nodes.Select(x => new RouteData { NodeName = x.Name, SlotFrom = x.SlotFrom, SlotTo = x.SlotTo }).ToList();
                    router = new MultipleNodeRouter(routes);
                    break;
                case PoolMode.Sentinel:
                    router = new SentinelNodeRouter();
                    break;
                default:
                    throw new ArgumentException("node pool mode error.");
            }
            return router;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                var clietnServers = m_pool.Values;
                if (clietnServers != null)
                {
                    foreach (var cs in clietnServers)
                    {
                        cs.Connection.Dispose();
                    }
                }
                clietnServers.Clear();
            }
        }
    }
}
